package io.github.wanggit.antrpc.client.monitor.report;

import io.github.wanggit.antrpc.commons.bean.IdGenHelper;
import io.github.wanggit.antrpc.commons.bean.SerialNumberThreadLocal;
import io.github.wanggit.antrpc.commons.utils.JsonUtils;
import io.github.wanggit.antrpc.commons.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Stack;

public class ZipkinAllMethodTracing {

    private static final Logger log = LoggerFactory.getLogger(ZipkinAllMethodTracing.class);

    private static final ThreadLocal<Stack<TracingPairs>> local = new ThreadLocal<>();

    public static boolean checkInTracingReady() {
        return SerialNumberThreadLocal.inRpcTracing()
                && (null != KafkaTracingSender.getInstance()
                        && KafkaTracingSender.getInstance().isReportAllMethodsToZipkin());
    }

    /**
     * 方法最前面执行
     *
     * @param className class name
     */
    public static void start(String className) {
        startNeverThrowEx(className);
    }

    private static void startNeverThrowEx(String className) {
        try {
            if (!checkInTracingReady()) {
                return;
            }
            if (log.isDebugEnabled()) {
                log.debug("Tracing start: " + className);
            }
            String requestId = IdGenHelper.getUUID();
            Stack<TracingPairs> stack = local.get();
            // 如果请求方法栈未初始化，那么把RPC的调用ID压栈
            if (null == stack || stack.empty()) {
                stack = new Stack<>();
                stack.push(
                        new TracingPairs(
                                SerialNumberThreadLocal.get().getCaller(),
                                SerialNumberThreadLocal.get().getSerialNumber()));
                local.set(stack);
            }
            local.get().push(new TracingPairs(requestId, local.get().peek().getSerialNumber()));
            if (log.isDebugEnabled()) {
                log.debug("Tracing start, push informations finished!!!");
                log.debug(JsonUtils.toJsonPrettyString(local.get()));
            }
        } catch (Exception e) {
            if (log.isWarnEnabled()) {
                log.warn("Start Tracing ERROR!!! " + e.getMessage());
            }
        }
    }

    /**
     * 方法最后跟踪
     *
     * @param className class name
     * @param methodName method name
     */
    public static void trace(String className, String methodName) {
        traceNeverThrowEx(className, methodName);
    }

    public static void traceNeverThrowEx(String className, String methodName) {
        try {
            if (!checkInTracingReady()) {
                return;
            }
            long end = Utils.currentSystemMicroSecond();
            if (log.isDebugEnabled()) {
                log.debug("Trace className=" + className + ", methodName=" + methodName);
            }
            Stack<TracingPairs> stack = local.get();
            if (null == stack) {
                // 在方法开始时，没有AntRPC环境，所以此次也不记录
                return;
            }
            TracingPairs pairs = stack.pop();
            TracingPairs parentPairs = stack.peek();
            KafkaTracingSender.getInstance()
                    .addTracingObj(
                            new TracingObj()
                                    .setBegin(pairs.ts)
                                    .setEnd(end)
                                    .setClassName(className)
                                    .setMethodName(methodName)
                                    .setCurrent(pairs)
                                    .setParent(parentPairs));
            if (log.isDebugEnabled()) {
                log.debug("Trace Finished...");
                log.debug(JsonUtils.toJsonPrettyString(local.get()));
            }
        } catch (Exception e) {
            if (log.isWarnEnabled()) {
                log.warn("End Tracing ERROR!!! " + e.getMessage());
            }
        }
    }

    public static TracingPairs getLastTracingPairs() {
        Stack<TracingPairs> tracingPairs = local.get();
        if (null != tracingPairs && !tracingPairs.empty()) {
            return tracingPairs.peek();
        }
        return null;
    }

    public static void cleanTracing() {
        if (null != local.get()) {
            local.get().clear();
        }
        local.remove();
        if (log.isDebugEnabled()) {
            log.debug("Clean Tracing Informations. ");
        }
    }

    static class TracingObj {
        // 微秒
        private long begin;
        // 微秒
        private long end;
        private String className;
        private String methodName;
        private TracingPairs current;
        private TracingPairs parent;

        public long getBegin() {
            return begin;
        }

        public long getEnd() {
            return end;
        }

        public String getClassName() {
            return className;
        }

        public String getMethodName() {
            return methodName;
        }

        public TracingPairs getCurrent() {
            return current;
        }

        public TracingPairs getParent() {
            return parent;
        }

        public TracingObj setParent(TracingPairs parent) {
            this.parent = parent;
            return this;
        }

        public TracingObj setCurrent(TracingPairs current) {
            this.current = current;
            return this;
        }

        public TracingObj setMethodName(String methodName) {
            this.methodName = methodName;
            return this;
        }

        public TracingObj setEnd(long end) {
            this.end = end;
            return this;
        }

        public TracingObj setClassName(String className) {
            this.className = className;
            return this;
        }

        public TracingObj setBegin(long begin) {
            this.begin = begin;
            return this;
        }
    }

    public static class TracingPairs {
        private final String requestId;
        private final String serialNumber;
        private final long ts;

        public TracingPairs(String requestId, String serialNumber) {
            this.requestId = requestId;
            this.serialNumber = serialNumber;
            ts = Utils.currentSystemMicroSecond();
        }

        public String getRequestId() {
            return requestId;
        }

        public String getSerialNumber() {
            return serialNumber;
        }

        public long getTs() {
            return ts;
        }
    }
}
